import './index.less'

import { Dropdown } from 'ant-design-vue'
import { uuid } from 'g6-fn'
import { createApp, h, ref } from 'vue'

import { textLabelTagApi } from '@/services/index'
import { hexToRgb } from '@/utils'

import DmpLabel from '../../../components/DmpLabel.vue'
import SelectEntityLabel from '../../../components/SelectEntityLabel.vue'
import { DmpLabelProps } from '../../../types/index'
import { DEFAULT_CONFIG, DefaultConfig, EntityLabelLocationInfo, ServiceParams, UpdateFunc } from './config'
import { CodeEnum } from './enum'

// 下拉框状态
export const dropdownState = ref<any>({
  modelOpen: false,
  open: false,
  'onUpdate:modelOpen': (val: boolean) => {
    dropdownState.value.modelOpen = val
  },
  'onUpdate:open': (val: boolean) => {
    dropdownState.value.open = val
  }
})

// 实体标签选择组件
const selectEntityLabelState = ref({
  list: [],
  loading: false
})

// 获取实体标签列表
const getEntityLabelList = async (params: any) => {
  try {
    selectEntityLabelState.value.loading = true
    const res = await textLabelTagApi.entityList(params)
    selectEntityLabelState.value.list = res.data
  } catch (error) {
  } finally {
    selectEntityLabelState.value.loading = false
  }
}

/**
 * 命名实体标注(三元组标注父类)
 */
export class TextNamedEntityLabel {
  isExistSubLine: boolean
  serviceParams: ServiceParams = {}
  container: HTMLElement | null = null
  currentTarget: EventTarget | null = null
  config: DefaultConfig
  updateEvent?: UpdateFunc
  // 启用or禁用 禁用后不可进行标注操作
  private enabled = true
  // 实体标签标注信息
  entityLabelLocationInfoList: EntityLabelLocationInfo[] = []
  // 下拉框组件
  dropdownComponent: any = null
  // 选区对象
  currentRange: Range | null = null
  // 当前标签容器
  currentLabelWrap: HTMLDivElement | null = null
  // 当前标签组件
  currentLabelComponent: any = null
  // 当前选中的文字
  currentSelectionText: string | null = null
  constructor(config: DefaultConfig = DEFAULT_CONFIG) {
    this.isExistSubLine = false
    this.enabled = config.enabled || true
    this.config = {
      ...DEFAULT_CONFIG,
      ...config,
      textSelectionStyle: {
        ...DEFAULT_CONFIG.textSelectionStyle,
        ...config.textSelectionStyle
      },
      labelBeforeSpanActiveStyle: {
        ...DEFAULT_CONFIG.labelBeforeSpanActiveStyle,
        ...config.labelBeforeSpanActiveStyle
      }
    }
  }

  /**
   * 初始化
   * @param {HTMLElement} container 容器
   * @param {HTMLElement} container 容器
   */
  init = (container: HTMLElement, serviceParams?: ServiceParams) => {
    this.container = container
    this.serviceParams = serviceParams || {}
    // 监听鼠标事件
    container.addEventListener('click', this.handleClick)
    // 监听鼠标按下
    container.addEventListener('mousedown', this.handleMouseDown)
    this.changeEnable(this.enabled)
  }

  /**
   * 设置启用状态
   */
  set enable(enable: boolean) {
    this.changeEnable(enable)
  }

  /**
   * 改变启用状态
   */
  private changeEnable = (enable: boolean) => {
    this.enabled = enable
    if (!this.container) {
      return
    }
    Object.assign(this.container.style, {
      pointerEvents: this.enabled ? 'auto' : 'none'
    })
  }

  // 鼠标按下事件
  handleMouseDown = () => {
    window.getSelection()?.removeAllRanges()
  }

  /**
   * 鼠标点击事件
   */
  handleClick = () => {
    const selection = window.getSelection()!
    if (!selection) {
      return
    }
    if (this.checkNodeIncluded(selection)) {
      selection.removeAllRanges()
      this.updateEvent?.({
        type: 'INFO',
        message: '选中的文字中包含标签',
        data: null,
        code: CodeEnum.TEXT_CONTAINS_LABEL
      })
      return
    }
    const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null
    if (!range) {
      return
    }
    this.currentRange = range
    const selectionText = range.toString()
    if (!selectionText) {
      return
    }
    // if (this.currentSelectionText === selectionText) {
    //   return
    // }
    this.currentSelectionText = selectionText
    const container = range.commonAncestorContainer
    if (container.nodeType === Node.ELEMENT_NODE) {
      const id = (container as Element).id
      if (id !== 'dmp-text-wrap') {
        this.updateEvent?.({
          type: 'ERROR',
          message: '选中操作不合法',
          data: null,
          code: CodeEnum.INVALID_OPERATION
        })
        return
      }
    }

    this.openSelectEntityLabel(range)
  }

  /**
   * 打开选择标签的弹窗组件
   */
  openSelectEntityLabel = (range: Range) => {
    if (!range) {
      return
    }
    // 获取选取文字
    const selectionText = range.toString()
    if (!selectionText) {
      return
    }

    dropdownState.value.open = true

    if (this.dropdownComponent) {
      try {
        this.dropdownComponent.unmount()
        this.dropdownComponent = null
      } catch (error) {
        console.log(error)
      }
    }
    getEntityLabelList({
      taskId: this.serviceParams.taskId
    })

    // 创建下拉框组件
    this.dropdownComponent = createApp({
      render: () =>
        h(
          Dropdown,
          {
            trigger: ['click'],
            open: dropdownState.value.open,
            'onUpdate:open': (val: boolean) => {
              dropdownState.value.open = val
              this.dropdownComponent.unmount()
              // this.currentRange = null
              this.dropdownComponent = null
            }
          },
          {
            default: () => h('span'),
            overlay: () =>
              h(SelectEntityLabel, {
                list: selectEntityLabelState.value.list,
                loading: selectEntityLabelState.value.loading,
                onMenuClick: (params: any) => {
                  dropdownState.value.open = false
                  this.currentRange = null
                  this.dropdownComponent.unmount()
                  this.insertTagByRange(range, {
                    id: params.id,
                    color: params.color,
                    title: params.name,
                    onlyId: uuid()
                  })
                },
                onClick: () => {
                  dropdownState.value.open = false
                  this.currentRange = null
                }
              })
          }
        )
    })

    const elFragment = document.createDocumentFragment()
    this.dropdownComponent.mount(elFragment)
    // 创建一个新的 Range
    const newRange = document.createRange()
    // 设置新 Range 的开始和结束位置
    newRange.setStart(range.endContainer, range.endOffset)
    newRange.setEnd(range.endContainer, range.endOffset)
    // 插入新元素
    newRange.insertNode(elFragment)
  }

  /**
   * 创建标签
   * @param label
   * @returns 标签虚拟DOM
   */
  createTag = (label: DmpLabelProps) => {
    // 创建一个新的Vue实例，并传入props数据
    const dmpLabelEl = h(DmpLabel, {
      title: '标题',
      color: '#1868E9',
      showDropdown: true,
      ...label,
      // 监听组件内部事件
      onRemove: async () => {
        // 删除标签
        await this.removeTag(dmpLabelWrap)
        dmpLabelComponent.unmount()
      },
      onClick: () => {
        this.currentLabelWrap = dmpLabelWrap
      }
    })
    const dmpLabelComponent = createApp({
      render: () =>
        h(
          Dropdown,
          {
            trigger: ['click'],
            onOpenChange: open => {
              dropdownState.value.open = false
              window.getSelection()?.removeAllRanges()
              if (open) {
                getEntityLabelList({
                  taskId: this.serviceParams.taskId
                })
                this.currentLabelWrap = dmpLabelWrap
                this.currentLabelComponent = dmpLabelComponent
                return
              }
              this.currentLabelWrap = null
              this.currentLabelComponent = null
            }
          },
          {
            default: () => dmpLabelEl,
            overlay: () =>
              h(SelectEntityLabel, {
                list: selectEntityLabelState.value.list,
                loading: selectEntityLabelState.value.loading,
                onMenuClick: async (params: any) => {
                  // 替换标签
                  await this.replaceTag(dmpLabelWrap, {
                    id: params.id,
                    color: params.color,
                    title: params.name
                  })
                  dmpLabelComponent.unmount()
                }
              })
          }
        )
    })

    // 创建一个新的元素
    const dmpLabelWrap = this.createLabelWrap(label)

    // 挂载到新的元素上
    dmpLabelComponent.mount(dmpLabelWrap)

    // 监听鼠标移入事件
    dmpLabelWrap.addEventListener('mouseenter', () => {
      let prevNode = dmpLabelWrap?.previousElementSibling as any
      while (prevNode && prevNode?.nodeName === 'SPAN' && prevNode?.getAttribute('data-selection') === 'true') {
        Object.assign(prevNode.style, {
          ...this.config.labelBeforeSpanActiveStyle,
          background: hexToRgb(label?.color, 0.1)
        })
        prevNode = prevNode.previousElementSibling
      }
    })
    // 监听鼠标移出事件
    dmpLabelWrap.addEventListener('mouseleave', () => {
      this.container?.querySelectorAll('span').forEach(span => {
        span.style.background = 'transparent'
      })
    })

    return dmpLabelWrap
  }

  /**
   * 替换标签
   * @param dmpLabelWrap 需要替换的标签容器
   * @param label 标签信息
   */
  replaceTag = (dmpLabelWrap: HTMLDivElement, label: DmpLabelProps) => {
    return new Promise<any>(resolve => {
      const onlyId = dmpLabelWrap.getAttribute('data-only-id') || ''
      const textList: string[] = this.removeSpan(dmpLabelWrap)
      const text = this.textListWrapSpan(textList, {
        color: label.color
      })
      const tag = this.createTag({ ...label, onlyId })
      dmpLabelWrap?.replaceWith(tag)
      let index = 0
      while (index < textList.length) {
        tag.previousSibling?.remove()
        index++
      }
      tag.before(text)
      dmpLabelWrap?.remove()
      this.currentLabelWrap = null

      const labelIndex = this.entityLabelLocationInfoList.findIndex(item => item.onlyId === onlyId)
      if (labelIndex !== -1) {
        this.entityLabelLocationInfoList[labelIndex] = {
          ...this.entityLabelLocationInfoList[labelIndex],
          labelId: label.id,
          labelName: label.title,
          labelColor: label.color
        }
      }

      this.updateEvent?.({
        type: 'SUCCESS',
        message: '标签替换成功',
        data: this.entityLabelLocationInfoList,
        code: CodeEnum.SUCCESS
      })

      this.currentLabelWrap = null
      resolve(label)
    })
  }

  /**
   * 插入到选中的文字到range位置
   */
  insertTagByRange = (range: Range, label: DmpLabelProps) => {
    if (!range) {
      return
    }
    const selectionText = range.toString()
    if (!selectionText) {
      return
    }
    const characters = selectionText.split('')

    const dmpLabelWrap = this.createTag(label)
    const textList = this.textListWrapSpan(characters, label)

    textList.appendChild(dmpLabelWrap)
    // 用包含了 <span> 元素的文档片段替换选中的内容
    range.deleteContents()
    range.insertNode(textList)

    this.entityLabelLocationInfoList.push({
      onlyId: label.onlyId,
      labelId: label.id,
      labelName: label.title,
      labelColor: label.color,
      text: selectionText
    })

    this.updateEvent?.({
      type: 'SUCCESS',
      message: '标签插入成功',
      data: this.entityLabelLocationInfoList,
      code: CodeEnum.SUCCESS
    })

    this.currentLabelWrap = null
  }

  /**
   *  传入一段文字，将其包裹在span标签中
   * @param text
   * @param label
   * @param style
   * @returns
   */
  textWrapSpan = (text: string, label: DmpLabelProps, style?: any) => {
    const span = document.createElement('span')
    span.setAttribute('data-selection', 'true')
    span.textContent = text
    Object.assign(span.style, {
      ...DEFAULT_CONFIG.textSelectionStyle,
      'border-bottom': '1px solid red',
      'border-bottom-color': label.color,
      padding: '6px 0',
      ...style
    })

    return span
  }

  /**
   * 删除标签
   * @param node 节点
   */
  removeTag = (node: HTMLDivElement) => {
    return new Promise(resolve => {
      const onlyId = node.getAttribute('data-only-id') || ''
      const labelIndex = this.entityLabelLocationInfoList.findIndex(item => item.onlyId === onlyId)
      if (labelIndex !== -1) {
        this.entityLabelLocationInfoList.splice(labelIndex, 1)
      }
      this.removeSpan(node)
      node.remove()

      this.updateEvent?.({
        type: 'SUCCESS',
        message: '标签删除成功',
        data: this.entityLabelLocationInfoList,
        code: CodeEnum.SUCCESS
      })
      resolve(this.entityLabelLocationInfoList)
    })
  }

  /**
   * 传入一段文字，将其每个字符包裹在span标签中
   * @param text
   * @returns node text
   */
  textListWrapSpan = (textList: string[], label?: DmpLabelProps) => {
    // 创建一个文档片段
    const fragment = document.createDocumentFragment()
    // 遍历每个字符，为其创建一个 <span> 元素并包裹起来
    textList.forEach((character: any, index: number) => {
      const span = this.textWrapSpan(character, label!, {
        'margin-left': index === 0 ? '4px' : '0'
      })
      fragment.appendChild(span)
    })
    return fragment
  }

  /**
   * 传入一个节点，将其节点之前的所有相连的span标签去掉
   * return 返回被移除掉的span标签的内容
   */
  removeSpan = (node: any) => {
    const textList: string[] = []
    let prevNode = node?.previousElementSibling
    while (prevNode && prevNode.nodeName === 'SPAN' && prevNode?.getAttribute('data-selection') === 'true') {
      const textNode = document.createTextNode(prevNode.textContent || '')
      prevNode.parentNode.replaceChild(textNode, prevNode)
      prevNode = node.previousElementSibling
      textList.unshift(textNode.textContent || '')
    }

    return textList
  }

  /**
   * 创建标签容器
   * @param label
   * @return {HTMLDivElement}
   */
  createLabelWrap = (label: DmpLabelProps) => {
    const dmpLabelWrap = document.createElement('div')
    dmpLabelWrap.setAttribute('class', 'label-text-tag-wrap')
    dmpLabelWrap.setAttribute('custom-data', 'TAG')
    dmpLabelWrap.setAttribute('label-id', label?.id || '')
    dmpLabelWrap.setAttribute('label-title', label?.title || '')
    dmpLabelWrap.setAttribute('data-only-id', label?.onlyId || '')
    Object.assign(dmpLabelWrap.style, {
      'border-bottom': '1px solid #1868E9',
      'border-bottom-color': label?.color
    })

    return dmpLabelWrap
  }

  /**
   * 检查选取的文字中是否存在某个节点
   * @param selection
   * @param nodeName
   * @returns {boolean}
   */
  checkNodeIncluded = (selection: Selection, nodeName = 'SPAN'): boolean => {
    if (selection.rangeCount === 0) {
      return false
    }
    const range = selection.getRangeAt(0)
    const el = range.cloneContents()
    const nodes = el.childNodes || []
    let isExistSpan = false
    nodes.forEach(node => {
      if (node.nodeName === nodeName.toUpperCase()) {
        isExistSpan = true
      }
    })
    return isExistSpan
  }

  /**
   * 获取容器中的标注信息
   * @returns {EntityLabelLocationInfo[]}
   */
  getLabelLocationInfo = (): Partial<EntityLabelLocationInfo>[] => {
    const textClassContent = document.getElementById('dmp-text-wrap')
    let startIndex = -1
    let textIndex = 0
    let labelInfo: EntityLabelLocationInfo = {}
    const labelList: EntityLabelLocationInfo[] = []
    textClassContent?.childNodes.forEach((item: any) => {
      if (item.nodeType === Node.TEXT_NODE) {
        textIndex += item.textContent.length
      }
      if (item.nodeName === 'SPAN') {
        if (startIndex === -1) {
          startIndex = textIndex
        }
        textIndex += item.textContent.length
      }
      if (item.nodeName === 'DIV' && item.getAttribute('custom-data') === 'TAG') {
        const labelId = item.getAttribute('label-id')
        const labelTitle = item.getAttribute('label-title')
        const onlyId = item.getAttribute('data-only-id')
        const data = this.entityLabelLocationInfoList.find(item => item.onlyId === onlyId) || {}

        Object.assign(labelInfo, {
          ...data,
          labelId,
          labelName: labelTitle,
          start: startIndex,
          end: textIndex - 1
        })
        labelList.push(labelInfo)
        labelInfo = {}
        startIndex = -1
      }
    })

    return labelList
  }

  /**
   * 传入一组标签信息，将其渲染到容器中
   */
  renderLabel = (labelList: EntityLabelLocationInfo[]) => {
    const textClassContent = document.getElementById('dmp-text-wrap')
    const textContent = textClassContent?.textContent
    const fragment = document.createDocumentFragment()
    let index = 0

    // 下一段文本内容
    let nextTextContent = textContent

    labelList.forEach(label => {
      const end = (label.end || 0) + 1
      const textBefore = nextTextContent?.slice(0, end - index)
      const textNodeBefore = document.createTextNode(textBefore || '')
      const textAfter = nextTextContent?.slice(end - index)
      fragment.appendChild(textNodeBefore)

      const dmpLabelWrap = this.createTag({
        id: label.labelId,
        title: label.labelName,
        color: label.labelColor,
        onlyId: label.onlyId
      })

      fragment.appendChild(dmpLabelWrap)

      const len = label.end! - label.start!
      const previousSibling = dmpLabelWrap.previousSibling as any
      if (previousSibling) {
        const fragment = document.createDocumentFragment()
        const index = previousSibling.textContent.length - 1 || 0
        const startText = previousSibling.textContent.slice(0, index - len)
        const endText = previousSibling.textContent.slice(index - len)
        const textNodeBefore = document.createTextNode(startText)
        const endTextList = endText.split('')
        const span = this.textListWrapSpan(endTextList, {
          color: label.labelColor
        })
        fragment.appendChild(textNodeBefore)
        fragment.appendChild(span)
        previousSibling.replaceWith(fragment)
      }
      nextTextContent = textAfter
      // 每次计算完后，将index指向下一个标签的开始位置
      index = label.end! + 1 || 0

      this.entityLabelLocationInfoList.push(label)
      this.updateEvent?.({
        type: 'SUCCESS',
        message: '标签渲染成功',
        data: this.entityLabelLocationInfoList,
        code: CodeEnum.SUCCESS
      })
    })

    const textNodeAfter = document.createTextNode(nextTextContent || '')
    fragment.appendChild(textNodeAfter)

    textClassContent?.replaceChildren(fragment)
  }

  /**
   * 监听状态更新
   */
  update = (callback: UpdateFunc) => {
    this.updateEvent = callback
  }

  /**
   * 删除所有选区
   */
  removeAllRanges = () => {
    window?.getSelection()?.removeAllRanges()
  }

  /**
   * 销毁
   */
  destroy = () => {
    this.container?.removeEventListener('click', this.handleClick)
    this.container?.removeEventListener('mousedown', this.handleMouseDown)
    this.container = null
    this.currentLabelComponent = null
    this.currentLabelWrap = null
    this.currentSelectionText = null
    this.updateEvent = undefined
    this.entityLabelLocationInfoList = []
  }
}
